<?php


namespace WenRuns\Laravel\Admin;


use Closure;
use Encore\Admin\Admin;
use Encore\Admin\Exception\Handler;
use Encore\Admin\Grid\Column as ColumnAlias;
use Encore\Admin\Grid\Tools\ColumnSelector as ColumnSelectorAlias;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use WenRuns\Laravel\Admin\Grid\Column;
use WenRuns\Laravel\Admin\Grid\ColumnSelector;
use WenRuns\Laravel\Admin\Grid\Exporters\ExportButton;
use WenRuns\Laravel\Admin\Grid\Exporters\WenExporter;
use WenRuns\Laravel\Admin\Grid\Model;
use WenRuns\Laravel\Admin\Grid\Tools;
use WenRuns\Laravel\Admin\Grid\Tools\Header;
use WenRuns\Laravel\Admin\Grid\Tools\Selector;
use WenRuns\Laravel\Laravel;

class Grid extends \Encore\Admin\Grid
{

    /**
     * @var string
     */
    protected $view = 'WenAdmin::grid.table';

    protected function initTools()
    {
        $this->tools = new Tools($this);

        return $this;
    }


    /**
     * @var array
     */
    public $mutators = [];

    /**
     * @var array
     */
    public $mutatorColumns = [];

    /**
     * @var array
     */
    public $mutatorNames = [];

    /**
     * 原默认字段排序
     * @var array
     */
    public $originColumnNames = [];


    /**
     * 排序
     * Summary of sorters
     * @var array
     */
    public $sorters = [];


    public function visibleColumns()
    {
        $visible = $this->getVisibleColumnsFromQuery();

        if (empty($visible)) {
            $visible = $this->columns;
        } else {
            array_push($visible, ColumnAlias::SELECT_COLUMN_NAME, ColumnAlias::ACTION_COLUMN_NAME);

            $visible = $this->columns->filter(function (ColumnAlias $column) use ($visible) {
                return in_array($column->getName(), $visible);
            });
        }
        return $visible->filter(function (ColumnAlias $column) {
            return !in_array($column->getName(), $this->mutatorColumns);
        });
    }

    /**
     * @return array
     */
    public function visibleColumnNames()
    {
        $visible = $this->getVisibleColumnsFromQuery();
        if (empty($visible)) {
            $visible = $this->columnNames ?: $this->originColumnNames;
        } else {
            array_push($visible, ColumnAlias::SELECT_COLUMN_NAME, ColumnAlias::ACTION_COLUMN_NAME);

            $visible = collect($this->columnNames ?: $this->originColumnNames)->filter(function ($column) use ($visible) {
                return in_array($column, $visible);
            })->toArray();
        }
        return array_diff($visible, $this->mutatorNames);
    }

    /**
     * @param string $keyName
     */
    public function setKeyName(string $keyName): void
    {
        $this->keyName = $keyName;
    }


    public function renderColumnSelector(): string
    {
        return (new ColumnSelector($this))->render();
    }

    /**
     * Grid constructor.
     * @param Eloquent $model
     * @param Closure|null $builder
     */
    public function __construct(Eloquent $model, Closure $builder = null)
    {
        $this->model = new Model($model, $this);
        $this->keyName = $model->getKeyName();
        $this->builder = $builder;

        $this->initialize();

        $this->callInitCallbacks();
        //        parent::__construct($model, $builder);
    }

    /**
     * @return string
     */
    public function renderExportButton()
    {
        return (new ExportButton($this))->render();
    }


    /**
     * Get the string contents of the grid view.
     *
     * @return string
     * @throws \Exception
     */
    public function render()
    {
        $this->handleExportRequest(true);
        try {
            $this->build();
        } catch (\Exception $e) {
            return Handler::renderException($e);
        }

        $this->callRenderingCallback();


        return Admin::component($this->view, $this->variables());
    }


    /**
     * @param int $head
     * @param int $tail
     */
    public function fixColumns(int $head, int $tail = -1)
    {
        if (isMobile()) {
            return;
        }
        parent::fixColumns($head, $tail);
        $script = <<<SCRIPT
function synchronousFixTableHeight(){
    let tbodyEle = document.querySelector(".table-main table tbody");
    let leftBodyEle = document.querySelector(".table-fixed-left tbody");
    let rightBodyEle = document.querySelector(".table-fixed-right tbody");
    if(leftBodyEle || rightBodyEle){
        Array.from(tbodyEle.children).forEach((ele, i)=>{
            if (!this.resizeObserver) {
                this.resizeObserver = {};
            }
            if (typeof ResizeObserver == 'undefined') {
                console.warn('由于无法监控表格高度变化，请不要使用grid->fixedColumn()方法，否则可能会出现页面位置错乱！！！');
            } else {
                this.resizeObserver[i] = new ResizeObserver(entries => {
                    let h = entries[0].contentRect.height;
                    if(leftBodyEle && leftBodyEle.children[i]){
                        leftBodyEle.children[i].style.height = h + "px";
                    }
                    if(rightBodyEle && rightBodyEle.children[i]){
                        rightBodyEle.children[i].style.height = h + "px";
                    }
                });
                this.resizeObserver[i].observe(ele);
            }
        });
    }
}
synchronousFixTableHeight();
SCRIPT;
        \Encore\Admin\Facades\Admin::script($script);
    }

    /**
     * Build the grid.
     *
     * @return void
     * @throws \Exception
     */
    public function build()
    {
        if ($this->builded) {
            return;
        }

        $this->applyQuery();

        $collection = $this->applyFilter(false);

        $this->addDefaultColumns();
        $this->resortColumns();

        Column::setOriginalGridModels($collection);

        $data = $collection->toArray();

        $this->columns->map(function (ColumnAlias $column) use (&$data) {
            $data = $column->fill($data);
            $this->columnNames[] = $column->getName();
        });

        $this->buildRows($data, $collection);

        $this->builded = true;
    }

    /**
     * @return array|Collection|mixed|void
     */
    public function applyQuery()
    {
        $this->applyQuickSearch();

        $this->applyColumnFilter();

        $this->applyColumnSearch();

        $this->applySelectorQuery();
    }

    /**
     * Apply column filter to grid query.
     *
     * @return void
     */
    protected function applyColumnFilter()
    {
        $this->columns->each->bindFilterQuery($this->model());
    }

    /**
     * @param string $name
     * @param string $label
     *
     * @return bool|\Encore\Admin\Grid|ColumnAlias|Column
     */
    public function column($name, $label = '')
    {
        if (Str::contains($name, '.')) {
            return $this->addRelationColumn($name, $label);
        }

        if (Str::contains($name, '->')) {
            return $this->addJsonColumn($name, $label);
        }

        return $this->__call($name, array_filter([$label]));
    }

    /**
     * Add a relation column to grid.
     *
     * @param string $name
     * @param string $label
     *
     * @return $this|bool|ColumnAlias|Grid
     */
    protected function addRelationColumn($name, $label = '')
    {
        list($relation, $column) = explode('.', $name);

        $model = $this->model()->eloquent();

        if (!method_exists($model, $relation) || !$model->{$relation}() instanceof Relations\Relation) {
            $class = get_class($model);

            admin_error("Call to undefined relationship [{$relation}] on model [{$class}].");

            return $this;
        }

        $name = ($this->shouldSnakeAttributes() ? Str::snake($relation) : $relation) . '.' . $column;

        $this->model()->with($relation);

        return $this->addColumn($name, $label)->setRelation($relation, $column);
    }

    /**
     * Add column to grid.
     *
     * @param string $column
     * @param string $label
     *
     * @return ColumnAlias|\Illuminate\Support\HigherOrderTapProxy|mixed
     */
    protected function addColumn($column = '', $label = '')
    {
        $column = new Column($column, $label);
        $column->setGrid($this);

        return tap($column, function ($value) {
            $this->columns->push($value);
        });
    }

    /**
     * Dynamically add columns to the grid view.
     *
     * @param $method
     * @param $arguments
     *
     * @return bool|ColumnAlias|\Illuminate\Support\HigherOrderTapProxy|mixed
     */
    public function __call($method, $arguments)
    {
        if (static::hasMacro($method)) {
            return $this->macroCall($method, $arguments);
        }

        $label = $arguments[0] ?? null;

        if ($this->model()->eloquent() instanceof MongodbModel) {
            return $this->addColumn($method, $label);
        }

        if ($column = $this->handleGetMutatorColumn($method, $label)) {
            return $column;
        }

        if ($column = $this->handleRelationColumn($method, $label)) {
            return $column;
        }

        return $this->addColumn($method, $label);
    }


    public function selector(\Closure $closure = null)
    {
        if (empty($closure)) {
            return $this->selector;
        }
        $this->selector = new Selector();

        call_user_func($closure, $this->selector);

        $this->header(function () {
            return $this->renderSelector();
        });

        return $this;
    }

    public function header(Closure $closure = null)
    {
        if (!$closure) {
            return $this->header;
        }

        $this->header[] = $closure;

        return $this;
    }


    public function renderHeader()
    {
        if (empty($this->header)) {
            return '';
        }

        return (new Header($this))->render();
    }

    /**
     * @param $columns
     * @param string $javascriptFn
     * @param int $mergeInput
     * @return $this
     */
    public function mergeColspan($columns = '*', $javascriptFn = 'false', $mergeInput = 2)
    {
        $this->addMergeColspanScript(...func_get_args());
        return $this;
    }

    /**
     * @param string $columns
     * @param string $javascriptFn
     * @param int $mergeInput
     * @return $this
     */
    protected function addMergeColspanScript($columns = '*', $javascriptFn = 'false', $mergeInput = 2)
    {
        $preg = '/^function\s*\([\s\S]*?\)\s*\{[\s\S]*\}/m';
        if (is_string($columns) && preg_match($preg, $columns)) {
            $javascriptFn = $columns;
            $columns = '["*"]';
            if (is_numeric($javascriptFn)) {
                $mergeInput = $javascriptFn;
            }
        } else {
            if (is_string($columns)) {
                $columns = explode(',', $columns);
            }
            $columns = json_encode($columns);
        }
        Laravel::loadJs('grid/grid.js');
        $script = <<<SCRIPT
$(function(){
    new mergeColspan({
        tableSelector: "#{$this->tableID}",
        mergeInput: {$mergeInput},
        columns: {$columns},
        fn: {$javascriptFn},
    });
});
SCRIPT;
        Admin::script($script);
        return $this;
    }


    /**
     * 获取导出实例
     * @return string|WenExporter
     */
    public function getExportInstance()
    {
        return $this->exporter;
    }


    protected function getVisibleColumnsFromQuery()
    {
        $requestColumn = request(ColumnSelectorAlias::SELECT_COLUMN_NAME);
        $columns = array_map(function ($val) {
            if (preg_match('/(.+?):(1|0)$/', $val, $res)) {
                if ($res[2] == 1) {
                    return $res[1];
                }
                return null;
            }
            return $val;
        }, $requestColumn ? explode(',', $requestColumn) : []);

        return array_filter($columns) ?:
            array_values(array_diff($this->originColumnNames, $this->hiddenColumns));
    }

    /**
     * @return $this
     * @throws \Exception
     */
    public function resortColumns(): Grid
    {
        if ($columns = request(ColumnSelectorAlias::SELECT_COLUMN_NAME)) {
            $columns = array_filter(array_map(function ($val) {
                if (preg_match('/(.+?):(1|0)$/', $val, $res)) {
                    return $res[1];
                }
                return $val;
            }, explode(',', $columns)));
            $fields = [];
            $i = 0;
            $arr = [];
            $this->columns()->each(function (ColumnAlias $column, $key) use (&$fields, $columns, &$i, &$arr) {
                $this->originColumnNames[] = $column->getName();
                $index = array_search($column->getName(), $columns);
                if ($index === false) {
                    $fields[$key] = $column;
                } else {
                    $fields[$key] = $i;
                    $arr[$index] = $column;
                    $i++;
                }
            });
            ksort($arr);
            $arr = array_values($arr);
            foreach ($fields as $i => $field) {
                if ($field instanceof ColumnAlias) {
                    continue;
                }
                $fields[$i] = $arr[$field];
            }
            $this->setColumns($fields);
        } else {
            $this->columns()->each(function (ColumnAlias $column) {
                $this->originColumnNames[] = $column->getName();
            });
        }
        return $this;
    }


    /**
     * @param array|Collection $columns
     * @return $this
     * @throws \Exception
     */
    public function setColumns($columns): Grid
    {
        if ($columns instanceof Collection) {
            $this->columns = $columns;
        } else if ($columns instanceof ColumnAlias) {
            $this->columns = new Collection([$columns]);
        } else if (
            is_array($columns) && empty(array_filter($columns, function ($item) {
                return !($item instanceof ColumnAlias);
            }))
        ) {
            $this->columns = new Collection($columns);
        } else {
            throw new \Exception('setColumn只接受参数为：数组[Encore\Admin\Grid\Column] 或 Illuminate\Support\Collection 或 Encore\Admin\Grid\Column');
        }
        return $this;
    }

    /**
     * @return array
     */
    public function getDefaultVisibleColumnNames()
    {
        return array_values(
            array_diff(
                $this->originColumnNames,
                $this->hiddenColumns,
                [ColumnAlias::SELECT_COLUMN_NAME, ColumnAlias::ACTION_COLUMN_NAME]
            )
        );
    }
}
